/*
 * graphics.cpp
 *
 *  Created on: 09 Nis 2012
 *      Author: hframe
 */

#include "graphics.h"

startnamespace(androidplus)startnamespace(ui)

t_int32 graphics::is_projection_matrix_loaded=0;
t_int32 graphics::is_transformation_matrix_loaded=0;


graphics::graphics(ANativeWindow *window) {
	display=EGL_NO_DISPLAY;
	surface=EGL_NO_SURFACE;
	context=EGL_NO_CONTEXT;
	native_window=window;

	initialize();
	projection_matrix=NULL;
	model_matrix=NULL;
	camera_matrix=NULL;

}


t_int32 graphics::initialize()
{

	EGLint lFormat, lNumConfigs, lErrorResult;
	EGLConfig lConfig;
	const EGLint lAttributes[] = {
		EGL_RENDERABLE_TYPE,
		EGL_OPENGL_ES2_BIT,
		EGL_BLUE_SIZE, 8,
		EGL_GREEN_SIZE, 8,
		EGL_RED_SIZE, 8,
		EGL_SURFACE_TYPE,
		EGL_WINDOW_BIT,
		EGL_NONE
	};
	const EGLint contextAttrs[] = {
		EGL_CONTEXT_CLIENT_VERSION, 2,
		EGL_NONE
	};

	display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
	if (display == EGL_NO_DISPLAY)goto ERROR;;
	if (!eglInitialize(display, NULL, NULL)) goto ERROR;
	if(!eglChooseConfig(display, lAttributes, &lConfig, 1,&lNumConfigs) || (lNumConfigs <= 0)) goto ERROR;
	if (!eglGetConfigAttrib(display, lConfig, EGL_NATIVE_VISUAL_ID, &lFormat)) goto ERROR;

	surface = eglCreateWindowSurface(display, lConfig, native_window, NULL);
	if (surface == EGL_NO_SURFACE) goto ERROR;
	context = eglCreateContext(display, lConfig, EGL_NO_CONTEXT,contextAttrs);
	if (context == EGL_NO_CONTEXT) goto ERROR;
	if (!eglMakeCurrent (display, surface,surface,context))goto ERROR;
	if(!eglQuerySurface(display, surface, EGL_WIDTH,&surface_width)) goto ERROR;
	if( !eglQuerySurface(display, surface,EGL_HEIGHT,&surface_height))goto ERROR;
	if( surface_width <= 0 || surface_height <= 0) goto ERROR;
	glEnable(GL_DEPTH_TEST);

	glViewport(0, 0, surface_width,surface_height);
	LOGI("w:%d  h:%d w/h:%f",surface_width,surface_height,surface_width*1.0f/surface_height);
	//glDepthRangef(1,-1);
	glFrontFace(GL_CW);
	glEnable(GL_CULL_FACE);
	ANativeWindow_setBuffersGeometry(native_window, 0, 0, lFormat);


	/*if(surface_width<surface_height)
	 projection_matrix =new drawing::matrix(ortho(-1.0f,1.0f,-1.0f*surface_height/surface_width,1.0f*surface_height/surface_width,-1.0f,1.0f));
	 else
	 projection_matrix =new drawing::matrix(ortho(-1.0f*surface_width/surface_height,1.0f*surface_width/surface_height,-1.0f,1.0f,-1.0f,1.0f));*/

	/*if(surface_width<surface_height)
	projection_matrix =new drawing::matrix(perspective(67,surface_width*1.0f/surface_height,0.1f,2.0f));
	else
	projection_matrix =new drawing::matrix(perspective(67,surface_height*1.0f/surface_width,0.1f,2.0f));*/

	//projection_matrix =new drawing::matrix(frustum(surface_width * -100.0f/surface_height,surface_width*100.0f/surface_height,-100.0f,100.0f,1.0f,200.0f));
    //camera_matrix=new drawing::matrix(lookat(0.0f,0.0f,101.0f,0.0f,0.f,0.0f,0.0f,1.0f,0.0f));


	LOGI("Graphics Initialized");
	return STATUS_OK;

	ERROR:
	check_error("initialize");
	LOGE("Error While Starting Graphics");
	return STATUS_ERROR;

}

void graphics::uninitialize()
{


	if (display != EGL_NO_DISPLAY) {
		eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);

		if (context != EGL_NO_CONTEXT) {
			eglDestroyContext(display, context);
			context = EGL_NO_CONTEXT;
		}

		if (surface != EGL_NO_SURFACE) {
			eglDestroySurface(display, surface);
			surface = EGL_NO_SURFACE;
		}

		eglTerminate(display);
		display = EGL_NO_DISPLAY;
	}
	is_projection_matrix_loaded=0;
	is_transformation_matrix_loaded=0;

}

graphics::~graphics() {
	LOGI("Graphics Destroying");
	uninitialize();
	LOGI("Graphics Destroyed");
	if(projection_matrix!=NULL)
	delete projection_matrix;
	if(camera_matrix!=NULL)
	delete camera_matrix;
	if(model_matrix!=NULL)
	delete model_matrix;
}

t_int32 graphics::get_height() {
	return surface_height;
}
t_int32 graphics::get_width() {
	return surface_width;
}
void graphics::flush()
{
	if (eglSwapBuffers(display, surface) != EGL_TRUE)
	LOGE("Error %d swapping buffers.", eglGetError());

}

GLuint graphics::load_shader(const char * src,GLenum type) {

	GLuint shader;
	GLint compiled;

	shader=glCreateShader(type);
	if(shader==0)
	return 0;
	glShaderSource(shader,1,&src,NULL);
	glCompileShader(shader);
	glGetShaderiv(shader,GL_COMPILE_STATUS,&compiled);
	if(!compiled) {
		GLint info_len=0;
		glGetShaderiv(shader,GL_INFO_LOG_LENGTH,&info_len);
		if(info_len>1) {
			char *info_log=(char*)malloc(sizeof(char)*info_len);
			glGetShaderInfoLog(shader,info_len,NULL,info_log);
			LOGE("Error compiling shader%s",info_log);
			free(info_log);
		}
		glDeleteShader(shader);
		return 0;
	}
	return shader;

}
void graphics::check_error(const char *msg)
{
	for (GLint error = glGetError(); error; error = glGetError()) {
		LOGI("after %s() glError (0x%x)\n", msg, error);
	}

}
GLint graphics::create_program(const char * program_name,const char *pVertexSource,const char *pFragmentSource)
{

	if(program_objects.count(program_name)==0) {

		GLuint vertexShader = load_shader(pVertexSource, GL_VERTEX_SHADER);
		if (!vertexShader) {
			return 0;
		}

		GLuint pixelShader = load_shader(pFragmentSource,GL_FRAGMENT_SHADER);
		if (!pixelShader) {
			return 0;
		}

		GLuint program = glCreateProgram();
		if (program) {
			glAttachShader(program, vertexShader);
			check_error("glAttachShader");
			glAttachShader(program, pixelShader);
			check_error("glAttachShader");
			glLinkProgram(program);
			GLint linkStatus = GL_FALSE;
			glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
			if (linkStatus != GL_TRUE) {
				GLint bufLength = 0;
				glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength);
				if (bufLength) {
					char* buf = (char*) malloc(bufLength);
					if (buf) {
						glGetProgramInfoLog(program, bufLength, NULL, buf);
						LOGE("Could not link program:\n%s\n", buf);
						free(buf);
					}
				}
				glDeleteProgram(program);
				program = -1;
			}
		}
		program_objects[program_name]=program;
	}

	return program_objects[program_name];
}

void graphics::draw_background(drawing::color c) {
	glClearColor(c.red,c.green,c.blue,c.alpha );
	glClear(GL_COLOR_BUFFER_BIT);
}

drawing::matrix graphics::frustum( t_float left,t_float right,t_float bottom, t_float top, t_float nearZ, t_float farZ) {

	t_float deltaX = right - left;
	t_float deltaY = top - bottom;
	t_float deltaZ = farZ - nearZ;
	drawing::matrix frust;

	if ( (nearZ <= 0.0f) || (farZ <= 0.0f) ||
			(deltaX <= 0.0f) || (deltaY <= 0.0f) || (deltaZ <= 0.0f) )
	return drawing::matrix::matrix_identity();

	frust[0] = 2.0f * nearZ / deltaX;
	frust[1] = frust[2] = frust[3] = 0.0f;

	frust[5] = 2.0f * nearZ / deltaY;
	frust[4] = frust[6] = frust[7] = 0.0f;

	frust[8] = 2.0f*(right + left) / deltaX;
	frust[9] = (top + bottom) / deltaY;
	frust[10] = -(nearZ + farZ) / deltaZ;
	frust[11] = -1.0f;

	frust[14] = -2.0f * nearZ * farZ / deltaZ;
	frust[12] = frust[13] = frust[15] = 0.0f;
	return frust;

}
drawing::matrix graphics::perspective(t_float fovy, t_float aspect, t_float nearZ, t_float farZ) {
#define PI 3.1415926535897932384626433832795f
	t_float frustumW, frustumH;

	frustumH = tanf( fovy / 360.0f * PI ) * nearZ;
	frustumW = frustumH * aspect;
	return frustum( -frustumW, frustumW, -frustumH, frustumH, nearZ, farZ );

}
drawing::matrix graphics::ortho(t_float left, t_float right, t_float bottom, t_float top, t_float farZ, t_float nearZ) {

	t_float deltaX = right - left;
	t_float deltaY = top - bottom;
	t_float deltaZ = farZ - nearZ;
	drawing::matrix ortho;

	if ( (deltaX == 0.0f) || (deltaY == 0.0f) || (deltaZ == 0.0f) )
	return drawing::matrix::matrix_identity();

	ortho[0] = 2.0f / deltaX;
	ortho[3] = -(right + left) / deltaX;
	ortho[5] = 2.0f / deltaY;
	ortho[7] = -(top + bottom) / deltaY;
	ortho[10] = -2.0f / deltaZ;
	ortho[11] = -(nearZ + farZ) / deltaZ;
	ortho[15]=1;

	return ortho;
}


drawing::matrix graphics::lookat(t_float eyeX, t_float eyeY,t_float eyeZ , t_float centerX , t_float centerY , t_float centerZ , t_float upX , t_float upY , t_float upZ) {
	        float fx = centerX - eyeX;
	        float fy = centerY - eyeY;
	        float fz = centerZ - eyeZ;
	        // Normalize f
	        float rlf = 1.0f /drawing::matrix::length(fx, fy, fz);
	        fx *= rlf;
	        fy *= rlf;
	        fz *= rlf;
	        // compute s = f x up (x means "cross product")

	        float sx = fy * upZ - fz * upY;
           float sy = fz * upX - fx * upZ;
	        float sz = fx * upY - fy * upX;
	        // and normalize s
	        float rls = 1.0f / drawing::matrix::length(sx, sy, sz);
	        sx *= rls;
	        sy *= rls;
	        sz *= rls;
	        // compute u = s x f
	        float ux = sy * fz - sz * fy;
           float uy = sz * fx - sx * fz;
	        float uz = sx * fy - sy * fx;
	        drawing::matrix rm;
	        rm[ 0] = sx;
           rm[1] = ux;
           rm[ 2] = -fx;
           rm[ 3] = 0.0f;
           rm[ 4] = sy;
	        rm[ 5] = uy;
	        rm[ 6] = -fy;
	        rm[ 7] = 0.0f;
	        rm[ 8] = sz;
	        rm[ 9] = uz;
	        rm[10] = -fz;
	        rm[11] = 0.0f;
	        rm[12] = 0.0f;
	        rm[13] = 0.0f;
	        rm[14] = 0.0f;
	        rm[15] = 1.0f;


	        return rm.transform(-eyeX,-eyeY,-eyeZ);


}

endnamespace(ui)
endnamespace(androidplus)
